import { Injectable, BadRequestException, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { LcProject, LcDocument } from '../../types/editor.type';
import { ITypeEntity } from '../../../superType/types';
import * as fs from 'fs';
import * as path from 'path';
import * as archiver from 'archiver';
import * as admZip from 'adm-zip';
import { getRuntimePath } from '@/utils/file';
import { cdnDomain } from '@/config/domain';
import { ResourceScannerService } from './services/resource-scanner.service';
import { ResourceDownloaderService } from './services/resource-downloader.service';
import { ResourceManifestService } from './services/resource-manifest.service';
import { MinioService } from './services/minio.service';
import { ProjectExportWithResources } from './types/resource.types';

export interface ProjectExportData {
  project: any;
  documents: any[];
  types: any[];
  exportTime: number;
  version: string;
}

export interface ExportOptions {
  uploadToMinio?: boolean;
  minioFileName?: string;
  keepLocalCopy?: boolean;
}

@Injectable()
export class ProjectExportService {
  private readonly logger = new Logger(ProjectExportService.name);

  constructor(
    @InjectModel('project') private projectModel: Model<LcProject>,
    @InjectModel('doc') private documentModel: Model<LcDocument>,
    @InjectModel('type') private typeModel: Model<ITypeEntity>,
    private resourceScanner: ResourceScannerService,
    private resourceDownloader: ResourceDownloaderService,
    private resourceManifest: ResourceManifestService,
    private minioService: MinioService,
  ) {}

  /**
   * 导出项目
   */
  async exportProject(
    projectId: string,
    fileName?: string,
    options: ExportOptions = {},
  ): Promise<{
    success: boolean;
    downloadUrl?: string;
    fileName?: string;
    fileSize?: number;
    minioUrl?: string;
    localPath?: string;
    error?: string;
  }> {
    let tempDir: string | null = null;

    try {
      // 验证项目是否存在
      const project = await this.projectModel.findById(projectId);
      if (!project) {
        return { success: false, error: '项目不存在' };
      }

      this.logger.log(`开始导出项目: ${projectId}`);

      // 创建临时目录
      const tempDirName = `export_${projectId}_${Date.now()}`;
      tempDir = await getRuntimePath(tempDirName);

      // 获取项目数据
      const documents = await this.documentModel.find({ projectId });
      const types = await this.typeModel.find({ projectId });

      // 扫描资源
      this.logger.log('扫描项目资源...');
      const allResources = this.resourceScanner.scanProjectResources(
        project,
        documents,
      );
      this.logger.log(`发现 ${allResources.length} 个资源`);

      // 下载资源
      this.logger.log('开始下载资源...');
      const { downloaded, failed } =
        await this.resourceDownloader.downloadResources(allResources, tempDir);

      if (failed.length > 0) {
        this.logger.warn(`有 ${failed.length} 个资源下载失败`);
        failed.forEach(({ resource, error }) => {
          this.logger.warn(`  ${resource.originalUrl}: ${error}`);
        });
      }

      // 创建资源清单
      const resourceManifest = this.resourceManifest.createManifest(
        allResources,
        downloaded,
        cdnDomain, // 记录当前CDN域名
      );

      // 保存资源清单
      this.resourceManifest.saveManifest(resourceManifest, tempDir);

      // 生成基础导出数据
      const baseExportData = {
        project: JSON.parse(JSON.stringify(project)),
        documents: documents.map((doc) => JSON.parse(JSON.stringify(doc))),
        types: types.map((type) => JSON.parse(JSON.stringify(type))),
        exportTime: Date.now(),
      };

      // 创建包含资源的导出数据
      const exportData = this.resourceManifest.createExportDataWithResources(
        baseExportData,
        resourceManifest,
      );

      // 保存JSON文件
      const jsonFileName = fileName
        ? fileName.replace(/\.zip$/, '')
        : `project_${projectId}`;
      const jsonFilePath = path.join(tempDir, `${jsonFileName}.json`);
      fs.writeFileSync(
        jsonFilePath,
        JSON.stringify(exportData, null, 2),
        'utf8',
      );

      // 创建ZIP文件
      this.logger.log('创建ZIP文件...');
      const zipFilePath = path.join(tempDir, `${jsonFileName}.zip`);
      const zipResult = await this.createZipFile(
        tempDir,
        jsonFileName,
        zipFilePath,
      );

      if (!zipResult.success) {
        return { success: false, error: zipResult.error };
      }

      // 读取ZIP文件内容
      const zipBuffer = fs.readFileSync(zipFilePath);

      let minioUrl: string | undefined;
      let downloadUrl: string | undefined;
      let localPath: string | undefined;
      let finalFilePath: string = zipFilePath;

      // 检查是否需要上传到Minio
      if (options.uploadToMinio && this.minioService.isAvailable()) {
        try {
          this.logger.log('正在上传到Minio...');
          const minioFileName = options.minioFileName || `${jsonFileName}.zip`;
          const uploadResult = await this.minioService.uploadProjectExport(
            zipBuffer,
            projectId,
            minioFileName,
          );
          minioUrl = uploadResult.url;
          this.logger.log(`Minio上传成功: ${minioUrl}`);
        } catch (error) {
          this.logger.error(`Minio上传失败: ${error.message}`);
          // Minio上传失败不影响本地文件保存
        }
      }

      // 根据选项决定是否保存本地文件
      if (options.keepLocalCopy !== false) {
        // 移动ZIP文件到可访问的目录 - 使用 public/temp
        const projectRoot = path.resolve(__dirname, '../../../../../');
        const publicDir = path.join(projectRoot, 'public', 'temp');

        console.log(`导出文件保存目录: ${publicDir}`);

        if (!fs.existsSync(publicDir)) {
          fs.mkdirSync(publicDir, { recursive: true });
          console.log(`创建导出目录: ${publicDir}`);
        }

        const publicZipPath = path.join(publicDir, `${jsonFileName}.zip`);
        fs.copyFileSync(zipFilePath, publicZipPath);
        finalFilePath = publicZipPath;
        localPath = `/temp/${jsonFileName}.zip`;
        downloadUrl = localPath;

        console.log(`导出文件已保存: ${publicZipPath}`);
      }

      // 清理临时目录
      this.cleanupTempDirectory(tempDir);

      // 生成文件大小信息
      const stats = fs.statSync(finalFilePath);

      // 生成处理报告
      const report = this.resourceManifest.generateReport(resourceManifest);
      this.logger.log(`导出完成:\n${report}`);

      const result: any = {
        success: true,
        fileName: `${jsonFileName}.zip`,
        fileSize: stats.size,
      };

      if (downloadUrl) {
        result.downloadUrl = downloadUrl;
      }

      if (minioUrl) {
        result.minioUrl = minioUrl;
      }

      if (localPath) {
        result.localPath = localPath;
      }

      return result;
    } catch (error) {
      this.logger.error('导出项目失败:', error);
      return { success: false, error: error.message };
    } finally {
      // 确保清理临时目录
      if (tempDir) {
        this.cleanupTempDirectory(tempDir);
      }
    }
  }

  /**
   * 导入项目
   */
  async importProject(
    zipFilePath: string,
    options: {
      overwrite?: boolean;
      namePrefix?: string;
    } = {},
  ): Promise<{
    success: boolean;
    projectId?: string;
    projectName?: string;
    stats?: {
      projects: number;
      documents: number;
      types: number;
    };
    error?: string;
  }> {
    console.log('process.env.MONGODB_URI: ', process.env.MONGODB_URI);
    if (
      !process.env.MONGODB_URI ||
      process.env.MONGODB_URI?.includes('120.26.225.198')
    ) {
      throw new BadRequestException('禁止导入');
    }
    try {
      this.logger.log(`开始导入项目，ZIP文件路径: ${zipFilePath}`);

      // 解压ZIP文件
      const tempDir = await getRuntimePath(`import_${Date.now()}`);
      this.logger.log(`解压ZIP文件到临时目录: ${tempDir}`);

      const zip = new admZip(zipFilePath);
      zip.extractAllTo(tempDir, true);

      // 列出解压后的文件
      const extractedFiles = fs.readdirSync(tempDir);
      this.logger.log(`解压完成的文件列表: ${extractedFiles.join(', ')}`);

      // 查找JSON文件
      const jsonFile = fs
        .readdirSync(tempDir)
        .find((file) => file.endsWith('.json'));
      if (!jsonFile) {
        this.cleanupTempDirectory(tempDir);
        return { success: false, error: 'ZIP文件中未找到有效的JSON数据文件' };
      }

      this.logger.log(`找到项目数据文件: ${jsonFile}`);

      // 读取JSON数据
      const jsonFilePath = path.join(tempDir, jsonFile);
      const jsonData = fs.readFileSync(jsonFilePath, 'utf8');
      this.logger.log(`读取JSON数据文件: ${jsonFilePath}, 大小: ${jsonData.length} 字符`);

      const exportData: ProjectExportData = JSON.parse(jsonData);
      this.logger.log(`解析项目数据成功，项目名: ${exportData.project?.title || '未知'}`);

      // 验证数据格式
      if (!this.validateExportData(exportData)) {
        this.cleanupTempDirectory(tempDir);
        return { success: false, error: '导出数据格式无效' };
      }

      this.logger.log(`项目数据验证通过: ${exportData.documents?.length || 0} 个文档, ${exportData.types?.length || 0} 个类型`);

      // 检查项目是否已存在
      const existingProject = await this.projectModel.findById(
        exportData.project._id,
      );
      if (existingProject && !options.overwrite) {
        this.cleanupTempDirectory(tempDir);
        return {
          success: false,
          error: '项目已存在，如需覆盖请设置 overwrite 参数',
        };
      }

      // 初始化URL映射（在更外层定义）
      let urlMapping = new Map<string, string>();

      // 检查是否包含资源文件夹
      const resourcesDir = path.join(tempDir, 'resources');
      let resourceFiles: string[] = [];

      if (fs.existsSync(resourcesDir)) {
        this.logger.log(`发现资源文件夹: ${resourcesDir}`);

        const getAllFiles = (dir: string, fileList: string[] = []) => {
          const files = fs.readdirSync(dir);
          files.forEach(file => {
            const filePath = path.join(dir, file);
            if (fs.statSync(filePath).isDirectory()) {
              getAllFiles(filePath, fileList);
            } else {
              fileList.push(filePath);
            }
          });
          return fileList;
        };

        resourceFiles = getAllFiles(resourcesDir);
        this.logger.log(`找到 ${resourceFiles.length} 个资源文件:`);

        // 显示前10个文件信息
        resourceFiles.forEach((file, index) => {
          if (index < 10) {
            const relativePath = path.relative(resourcesDir, file);
            const stats = fs.statSync(file);
            this.logger.log(`  ${index + 1}. ${relativePath} (${stats.size} bytes)`);
          }
        });

        if (resourceFiles.length > 10) {
          this.logger.log(`  ... 还有 ${resourceFiles.length - 10} 个文件`);
        }

        // 检查Minio服务是否可用
        const minioAvailable = this.minioService && this.minioService.isAvailable();
        const storageType = process.env.STORAGE_TYPE;

        this.logger.log(`当前存储类型: ${storageType}`);
        this.logger.log(`Minio服务状态: ${minioAvailable ? '可用' : '不可用'}`);

        if (minioAvailable && storageType === 'minio') {
          this.logger.log('检测到Minio服务且配置为Minio存储，开始上传资源文件...');

          // 1. 先上传文件到Minio（存储文件）
          await this.uploadResourcesToMinio(resourcesDir, resourceFiles);
          this.logger.log('资源文件上传到Minio完成');

          // 2. 从manifest.json构建URL映射（用于URL替换）
          const manifestFile = path.join(resourcesDir, 'manifest.json');
          if (fs.existsSync(manifestFile)) {
            try {
              const manifestData = fs.readFileSync(manifestFile, 'utf8');
              const manifest = JSON.parse(manifestData);
              const currentCdnDomain = process.env.CDN_DOMAIN || 'cdn.appthen.com';

              this.logger.log(`从manifest构建URL映射，当前CDN域名: ${currentCdnDomain}`);

              // 基于manifest构建映射：原始CDN -> 当前CDN
              manifest.files.forEach((file: any) => {
                if (file.originalUrl && file.originalUrl.startsWith('http')) {
                  try {
                    const urlObj = new URL(file.originalUrl);
                    const newUrl = `https://${currentCdnDomain}${urlObj.pathname}${urlObj.search}`;

                    // 验证URL的完整性，避免不完整的URL
                    if (urlObj.pathname && urlObj.pathname !== '/') {
                      urlMapping.set(file.originalUrl, newUrl);
                    } else {
                      this.logger.warn(`跳过不完整的URL: ${file.originalUrl}`);
                    }
                  } catch (error) {
                    this.logger.warn(`解析URL失败: ${file.originalUrl}`, error);
                  }
                }
              });

              this.logger.log(`从manifest构建了 ${urlMapping.size} 个URL映射`);
            } catch (error) {
              this.logger.error('读取manifest失败', error);
            }
          } else {
            this.logger.warn('未找到manifest文件');
          }
        } else {
          this.logger.log(`Minio服务不可用或存储类型为${storageType}，将资源复制到本地temp目录`);
          await this.copyResourcesToLocalTemp(resourcesDir, resourceFiles);

          // 本地复制时，构建本地URL映射
          resourceFiles.forEach(file => {
            const relativePath = path.relative(resourcesDir, file);
            const originalUrl = this.extractOriginalUrlFromPath(relativePath);
            if (originalUrl) {
              urlMapping.set(originalUrl, `/temp/${relativePath}`);
            }
          });
        }
      } else {
        this.logger.log('未找到资源文件夹，可能导出时不包含资源文件');
      }

      // 检查是否包含资源清单
      const manifestFile = path.join(tempDir, 'resource-manifest.json');
      if (fs.existsSync(manifestFile)) {
        this.logger.log('发现资源清单文件');
        const manifestData = fs.readFileSync(manifestFile, 'utf8');
        this.logger.log(`资源清单大小: ${manifestData.length} 字符`);

        try {
          const manifest = JSON.parse(manifestData);
          this.logger.log(`资源清单解析成功，包含 ${manifest.files?.length || 0} 个资源记录`);
          this.logger.log(`原始CDN域名: ${manifest.originalCdnDomain}`);
          this.logger.log(`导出时间: ${new Date(manifest.exportTime).toLocaleString()}`);

          // 实现资源URL替换逻辑
          this.logger.log('开始处理资源URL替换...');

          // 获取当前环境的CDN域名
          const currentCdnDomain = process.env.CDN_DOMAIN || 'cdn.appthen.com';
          this.logger.log(`当前CDN域名: ${currentCdnDomain}`);
          this.logger.log(`URL映射数量: ${urlMapping.size}`);

          // 检查是否已有URL映射（来自Minio上传）
          if (urlMapping.size === 0) {
            this.logger.log('URL映射为空，从资源清单构建映射...');
            // 只有在没有URL映射时才从manifest构建（用于本地复制场景）
            manifest.files.forEach(resource => {
              if (resource.localPath) {
                // 将资源URL映射到本地相对路径
                urlMapping.set(resource.originalUrl, `/temp/${resource.localPath}`);
                this.logger.log(`清单URL映射: ${resource.originalUrl} -> /temp/${resource.localPath}`);
              }
            });
          } else {
            this.logger.log(`使用已有的URL映射，共 ${urlMapping.size} 个（来自Minio上传）`);
            // 显示现有的Minio映射
            urlMapping.forEach((newUrl, originalUrl) => {
              this.logger.log(`Minio URL映射: ${originalUrl} -> ${newUrl}`);
            });
          }

          // 更新项目数据中的资源URL引用
          this.logger.log('开始更新项目中的资源URL...');

          // 使用通用URL替换方法
          const updatedCount = this.performUrlReplacement(exportData, urlMapping);
          this.logger.log(`资源URL替换完成，共更新 ${updatedCount} 个引用`);
        } catch (error) {
          this.logger.error('解析资源清单失败:', error);
        }
      } else {
        this.logger.log('未找到资源清单文件');

        // 尝试从 resources 目录查找
        const resourcesManifestFile = path.join(tempDir, 'resources', 'manifest.json');
        if (fs.existsSync(resourcesManifestFile)) {
          this.logger.log('在 resources 目录中找到资源清单文件');
          const manifestData = fs.readFileSync(resourcesManifestFile, 'utf8');
          this.logger.log(`资源清单大小: ${manifestData.length} 字符`);

          try {
            const manifest = JSON.parse(manifestData);
            this.logger.log(`资源清单解析成功，包含 ${manifest.files?.length || 0} 个资源记录`);
            this.logger.log(`原始CDN域名: ${manifest.originalCdnDomain}`);
            this.logger.log(`导出时间: ${new Date(manifest.exportTime).toLocaleString()}`);

            // 实现资源URL替换逻辑
            this.logger.log('开始处理资源URL替换...');

            // 获取当前环境的CDN域名
            const currentCdnDomain = process.env.CDN_DOMAIN || 'cdn.appthen.com';
            this.logger.log(`当前CDN域名: ${currentCdnDomain}`);

            // 检查是否已有URL映射（来自Minio上传）
            if (urlMapping.size === 0) {
              this.logger.log('URL映射为空，从资源清单构建映射...');
              // 只有在没有URL映射时才从manifest构建（用于本地复制场景）
              manifest.files.forEach(resource => {
                if (resource.localPath) {
                  // 将资源URL映射到本地相对路径
                  urlMapping.set(resource.originalUrl, `/temp/${resource.localPath}`);
                  this.logger.log(`清单URL映射: ${resource.originalUrl} -> /temp/${resource.localPath}`);
                }
              });
            } else {
              this.logger.log(`使用已有的URL映射，共 ${urlMapping.size} 个（来自Minio上传）`);
              // 显示现有的Minio映射
              urlMapping.forEach((newUrl, originalUrl) => {
                this.logger.log(`Minio URL映射: ${originalUrl} -> ${newUrl}`);
              });
            }

            // 更新项目数据中的资源URL引用
            this.logger.log('开始更新项目中的资源URL...');

            // 使用通用URL替换方法
            const updatedCount = this.performUrlReplacement(exportData, urlMapping);
            this.logger.log(`资源URL替换完成，共更新 ${updatedCount} 个引用`);

          } catch (error) {
            this.logger.error('解析资源清单失败:', error);
          }
        }
      }

      // 导入数据
      this.logger.log('开始导入项目数据到数据库...');
      const importResult = await this.importProjectData(exportData, options);
      this.logger.log(`项目数据导入完成，新项目ID: ${importResult.projectId}`);

      // 清理临时目录
      this.cleanupTempDirectory(tempDir);

      return {
        success: true,
        projectId: importResult.projectId,
        projectName: importResult.projectName,
        stats: {
          projects: 1,
          documents: exportData.documents.length,
          types: exportData.types.length,
        },
      };
    } catch (error) {
      console.error('导入项目失败:', error);
      return { success: false, error: error.message };
    }
  }

  /**
   * 生成导出数据
   */
  private async generateExportData(
    projectId: string,
  ): Promise<ProjectExportData> {
    // 获取项目数据
    const project = await this.projectModel.findById(projectId);
    if (!project) {
      throw new BadRequestException('项目不存在');
    }

    // 获取项目文档
    const documents = await this.documentModel.find({ projectId });

    // 获取项目类型
    const types = await this.typeModel.find({ projectId });

    return {
      project: JSON.parse(JSON.stringify(project)),
      documents: documents.map((doc) => JSON.parse(JSON.stringify(doc))),
      types: types.map((type) => JSON.parse(JSON.stringify(type))),
      exportTime: Date.now(),
      version: '1.0.0',
    };
  }

  /**
   * 创建ZIP文件
   */
  private async createZipFile(
    sourceDir: string,
    jsonFileName: string,
    zipFilePath: string,
  ): Promise<{ success: boolean; error?: string }> {
    return new Promise((resolve) => {
      const output = fs.createWriteStream(zipFilePath);
      const archive = archiver('zip', { zlib: { level: 9 } });

      output.on('close', () => {
        resolve({ success: true });
      });

      archive.on('error', (err) => {
        this.logger.error('ZIP创建失败:', err);
        resolve({ success: false, error: err.message });
      });

      archive.pipe(output);

      // 添加JSON文件
      archive.file(path.join(sourceDir, `${jsonFileName}.json`), {
        name: `${jsonFileName}.json`,
      });

      // 添加资源文件
      const resourcesDir = path.join(sourceDir, 'resources');
      if (fs.existsSync(resourcesDir)) {
        archive.directory(resourcesDir, 'resources');
      }

      archive.finalize();
    });
  }

  /**
   * 验证导出数据格式
   */
  private validateExportData(data: any): boolean {
    return (
      data &&
      data.project &&
      data.documents &&
      Array.isArray(data.documents) &&
      data.types &&
      Array.isArray(data.types) &&
      data.exportTime &&
      data.version
    );
  }

  /**
   * 导入项目数据
   */
  private async importProjectData(
    exportData: ProjectExportData,
    options: {
      overwrite?: boolean;
      namePrefix?: string;
    },
  ) {
    const { project, documents, types } = exportData;

    this.logger.log(`开始导入项目数据: ${project.title}`);
    this.logger.log(`项目信息: ID=${project._id}, 文档数=${documents.length}, 类型数=${types.length}`);

    // 检查是否已存在同名项目
    const existingProject = await this.projectModel.findById(project._id);
    let finalProject = project;

    if (existingProject) {
      if (options.overwrite) {
        // 覆盖模式：删除现有数据
        await this.documentModel.deleteMany({ projectId: project._id });
        await this.typeModel.deleteMany({ projectId: project._id });
      } else {
        // 不覆盖：修改项目ID和相关关联
        const newProjectId = new Types.ObjectId().toString();
        finalProject = {
          ...project,
          _id: newProjectId,
          title: options.namePrefix
            ? `${options.namePrefix}${project.title}`
            : `${project.title}_副本`,
        };

        // 更新文档和类型中的项目ID
        documents.forEach((doc) => {
          doc.projectId = newProjectId;
        });
        types.forEach((type) => {
          type.projectId = newProjectId;
        });
      }
    }

    // 插入项目数据
    await this.projectModel.findByIdAndUpdate(finalProject._id, finalProject, {
      upsert: true,
      new: true,
    });

    // 插入文档数据
    if (documents.length > 0) {
      await this.documentModel.insertMany(documents, { ordered: false });
    }

    // 插入类型数据
    if (types.length > 0) {
      await this.typeModel.insertMany(types, { ordered: false });
    }

    this.logger.log(`项目数据导入成功: ${finalProject.title} (${finalProject._id})`);

    return {
      projectId: finalProject._id,
      projectName: finalProject.title,
    };
  }

  /**
   * 执行URL替换（安全的结构化替换）
   */
  private performUrlReplacement(exportData: ProjectExportData, urlMapping: Map<string, string>): number {
    let updatedCount = 0;

    // 获取当前环境的CDN域名
    const currentCdnDomain = process.env.CDN_DOMAIN || 'cdn.appthen.com';

    // 基于manifest中的originalUrl构建新的映射：原始CDN -> 当前CDN
    const newUrlMapping = new Map<string, string>();
    urlMapping.forEach((newUrl, originalUrl) => {
      // 只处理http/https开头的URL（CDN URL），跳过hash
      if (originalUrl.startsWith('http://') || originalUrl.startsWith('https://')) {
        // 构建新的URL：将原始URL的域名替换为当前CDN域名
        try {
          const urlObj = new URL(originalUrl);
          const newUrl = `https://${currentCdnDomain}${urlObj.pathname}${urlObj.search}`;
          newUrlMapping.set(originalUrl, newUrl);
        } catch (error) {
          this.logger.error(`解析URL失败: ${originalUrl}`, error);
        }
      }
    });

    this.logger.log(`构建了 ${newUrlMapping.size} 个新的URL映射`);

    // 安全的结构化替换，只替换JSON字段值
    updatedCount += this.replaceUrlsInObject(exportData.project, newUrlMapping, 'project');

    exportData.documents.forEach((doc, index) => {
      updatedCount += this.replaceUrlsInObject(doc, newUrlMapping, `document[${index}]`);
    });

    exportData.types.forEach((type, index) => {
      updatedCount += this.replaceUrlsInObject(type, newUrlMapping, `type[${index}]`);
    });

    this.logger.log(`结构化URL替换完成，共更新 ${updatedCount} 个引用`);
    return updatedCount;
  }

  /**
   * 在对象中递归替换URL
   */
  private replaceUrlsInObject(obj: any, urlMapping: Map<string, string>, context: string): number {
    let updatedCount = 0;

    if (!obj || typeof obj !== 'object') {
      return updatedCount;
    }

    // 处理数组
    if (Array.isArray(obj)) {
      obj.forEach((item, index) => {
        updatedCount += this.replaceUrlsInObject(item, urlMapping, `${context}[${index}]`);
      });
      return updatedCount;
    }

    // 处理对象
    for (const [key, value] of Object.entries(obj)) {
      const currentContext = `${context}.${key}`;

      if (typeof value === 'string') {
        // 只在字符串值中替换URL，避免替换代码中的URL
        updatedCount += this.replaceUrlsInString(value, urlMapping, currentContext, (newValue) => {
          obj[key] = newValue;
        });
      } else if (Array.isArray(value)) {
        // 处理数组，特别是urls数组
        updatedCount += this.replaceUrlsInArray(value, urlMapping, currentContext);
      } else if (typeof value === 'object' && value !== null) {
        // 递归处理嵌套对象
        updatedCount += this.replaceUrlsInObject(value, urlMapping, currentContext);
      }
    }

    return updatedCount;
  }

  /**
   * 在字符串中替换URL（只替换独立的URL，不替换代码中的URL）
   */
  private replaceUrlsInString(
    str: string,
    urlMapping: Map<string, string>,
    context: string,
    updateCallback: (newValue: string) => void
  ): number {
    let updatedCount = 0;
    let modifiedString = str;

    urlMapping.forEach((newUrl, originalUrl) => {
      // 只有当字符串完全匹配URL，或者URL在字符串中作为独立值出现时才替换
      // 避免替换代码片段或URL的一部分
      if (modifiedString === originalUrl) {
        // 完全匹配
        modifiedString = newUrl;
        updatedCount++;
        this.logger.log(`${context}: 完全匹配替换 ${originalUrl} -> ${newUrl}`);
      } else if (modifiedString.includes(originalUrl)) {
        // 简单替换，但避免在代码中间替换
        // 只要URL存在于字符串中就替换，这样更安全
        const oldString = modifiedString;
        modifiedString = modifiedString.replace(new RegExp(originalUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newUrl);

        if (oldString !== modifiedString) {
          updatedCount++;
          this.logger.log(`${context}: 字符串中的URL替换 ${originalUrl} -> ${newUrl}`);
        }
      }
    });

    if (modifiedString !== str) {
      updateCallback(modifiedString);
    }

    return updatedCount;
  }

  /**
   * 在数组中替换URL
   */
  private replaceUrlsInArray(
    arr: any[],
    urlMapping: Map<string, string>,
    context: string
  ): number {
    let updatedCount = 0;

    arr.forEach((item, index) => {
      const itemContext = `${context}[${index}]`;

      if (typeof item === 'string') {
        // 字符串项，直接替换
        updatedCount += this.replaceUrlsInString(item, urlMapping, itemContext, (newValue) => {
          arr[index] = newValue;
        });
      } else if (typeof item === 'object' && item !== null) {
        // 对象项，递归处理
        updatedCount += this.replaceUrlsInObject(item, urlMapping, itemContext);
      } else if (Array.isArray(item)) {
        // 嵌套数组，递归处理
        updatedCount += this.replaceUrlsInArray(item, urlMapping, itemContext);
      }
    });

    return updatedCount;
  }

  /**
   * 从文件路径提取原始URL
   */
  private extractOriginalUrlFromPath(relativePath: string): string | null {
    try {
      // 移除资源类型前缀，如 image/, script/, style/ 等
      const cleanPath = relativePath.replace(/^(image|script|style|font|video|audio)\//, '');

      // 检查是否是纯hash文件
      const pathParts = cleanPath.split('/');
      const fileName = pathParts[pathParts.length - 1];

      if (/^[A-Za-z0-9_-]{15,}$/.test(fileName) && !fileName.includes('.')) {
        // 纯hash文件
        return fileName;
      } else {
        // 路径类型文件，构建完整URL
        return `https://${process.env.CDN_DOMAIN}/${cleanPath}`;
      }
    } catch (error) {
      this.logger.error(`提取原始URL失败: ${relativePath}`, error);
      return null;
    }
  }

  /**
   * 上传资源到Minio（只负责上传，不返回URL映射）
   */
  private async uploadResourcesToMinio(resourcesDir: string, resourceFiles: string[], originalCdnDomain: string = 'cdn.appthen.com'): Promise<void> {
    try {
      const uploadedResources: Array<{
        buffer: Buffer;
        originalUrl: string;
        fileName: string;
      }> = [];

      // 创建URL映射表
      const urlMapping = new Map<string, string>();

      this.logger.log('准备上传资源文件到Minio...');

      // 准备上传数据
      for (const file of resourceFiles) {
        try {
          const relativePath = path.relative(resourcesDir, file);
          const stats = fs.statSync(file);

          // 读取文件内容
          const buffer = fs.readFileSync(file);

          // 根据文件路径推断原始URL
          let originalUrl = '';
          const fileName = path.basename(relativePath, path.extname(relativePath));

          // 检查是否是纯hash文件（无扩展名且文件名是长hash）
          if (path.extname(relativePath) === '' && /^[A-Za-z0-9_-]{15,}$/.test(fileName)) {
            // 纯hash：保持原样，如 FuoPS2xYMgbdOYHtwkWITu0RmaKR
            originalUrl = fileName;
          } else {
            // 路径类型：构建完整URL
            // 移除 resources/type/ 前缀，得到相对路径
            const cleanPath = relativePath.replace(/^image\/|script\/|style\/|font\/|video\/|audio\//, '');
            originalUrl = `https://${originalCdnDomain}/${cleanPath}`;
          }

          uploadedResources.push({
            buffer,
            originalUrl,
            fileName: path.basename(file),
          });

          if (uploadedResources.length % 20 === 0) {
            this.logger.log(`已准备 ${uploadedResources.length} 个文件待上传...`);
          }
        } catch (error) {
          this.logger.error(`准备文件失败: ${file}`, error);
        }
      }

      this.logger.log(`开始批量上传 ${uploadedResources.length} 个资源文件到Minio...`);

      // 使用Minio服务批量上传
      const results = await this.minioService.uploadProjectResources(
        uploadedResources,
        'import-project' // 使用一个通用的项目ID
      );

      // 分析上传结果
      const uploaded = results.filter((r: any) => r.url && !r.error);
      const failed = results.filter((r: any) => !r.url || r.error);

      this.logger.log(`Minio上传完成: ${uploaded.length} 个成功, ${failed.length} 个失败`);

      // 记录失败的上传
      if (failed.length > 0) {
        failed.forEach((failed: any) => {
          this.logger.warn(`上传失败: ${failed.originalUrl} - ${failed.error}`);
        });
      }

      // 不返回URL映射，因为URL映射由manifest构建
    } catch (error) {
      this.logger.error('上传资源到Minio失败:', error);
      throw error; // 重新抛出错误，让调用方知道上传失败
    }
  }

  /**
   * 复制资源到本地temp目录
   */
  private async copyResourcesToLocalTemp(resourcesDir: string, resourceFiles: string[]): Promise<void> {
    try {
      // 修复路径计算 - 确保使用项目根目录
      let projectRoot: string;
      if (__dirname.includes('/dist/')) {
        // 如果在编译后的目录中，需要向上更多层
        projectRoot = path.resolve(__dirname, '../../../../../../');
      } else {
        // 如果在源代码目录中
        projectRoot = path.resolve(__dirname, '../../../../../');
      }

      const publicTempDir = path.join(projectRoot, 'public', 'temp');

      this.logger.log(`复制资源到本地目录: ${publicTempDir}`);
      this.logger.log(`项目根目录: ${projectRoot}`);

      // 确保目录存在
      if (!fs.existsSync(publicTempDir)) {
        fs.mkdirSync(publicTempDir, { recursive: true });
        this.logger.log(`创建目录: ${publicTempDir}`);
      }

      let copiedCount = 0;
      resourceFiles.forEach((file, index) => {
        const relativePath = path.relative(resourcesDir, file);
        const stats = fs.statSync(file);
        const targetPath = path.join(publicTempDir, relativePath);

        // 确保目标目录存在
        const targetDir = path.dirname(targetPath);
        if (!fs.existsSync(targetDir)) {
          fs.mkdirSync(targetDir, { recursive: true });
        }

        // 复制文件
        fs.copyFileSync(file, targetPath);
        copiedCount++;

        if (index < 10) {
          this.logger.log(`  复制: ${relativePath} -> ${targetPath}`);
        }
      });

      this.logger.log(`资源复制完成: ${copiedCount} 个文件复制到 ${publicTempDir}`);
      this.logger.log(`复制后的文件应该可以通过 /temp/xxx 访问`);
    } catch (error) {
      this.logger.error('复制资源到本地目录失败:', error);
    }
  }

  /**
   * 清理临时目录
   */
  private cleanupTempDirectory(dirPath: string): void {
    try {
      if (fs.existsSync(dirPath)) {
        fs.rmSync(dirPath, { recursive: true, force: true });
      }
    } catch (error) {
      console.error('清理临时目录失败:', error);
    }
  }

  /**
   * 仅上传到Minio（不保存本地文件）
   */
  async uploadToMinioOnly(
    projectId: string,
    fileName?: string,
  ): Promise<{
    success: boolean;
    minioUrl?: string;
    fileName?: string;
    fileSize?: number;
    error?: string;
  }> {
    try {
      const result = await this.exportProject(projectId, fileName, {
        uploadToMinio: true,
        keepLocalCopy: false,
      });

      if (result.success) {
        return {
          success: true,
          minioUrl: result.minioUrl,
          fileName: result.fileName,
          fileSize: result.fileSize,
        };
      } else {
        return {
          success: false,
          error: result.error,
        };
      }
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }

  /**
   * 批量上传项目资源到Minio
   */
  async uploadProjectResourcesToMinio(
    projectId: string,
    resources: Array<{
      buffer: Buffer;
      originalUrl: string;
      fileName: string;
    }>,
  ): Promise<{
    success: boolean;
    uploaded: Array<{ originalUrl: string; url: string; key: string }>;
    failed: Array<{ originalUrl: string; error: string }>;
    error?: string;
  }> {
    if (!this.minioService.isAvailable()) {
      return {
        success: false,
        uploaded: [],
        failed: [],
        error: 'Minio服务不可用',
      };
    }

    try {
      this.logger.log(`开始上传 ${resources.length} 个项目资源到Minio...`);
      const results = await this.minioService.uploadProjectResources(
        resources,
        projectId,
      );

      const uploaded = results.filter((r) => r.url && !('error' in r));
      const failed = results
        .filter((r) => 'error' in r)
        .map((r) => ({
          originalUrl: r.originalUrl,
          error: (r as any).error,
        }));

      this.logger.log(
        `资源上传完成: ${uploaded.length} 成功, ${failed.length} 失败`,
      );

      return {
        success: true,
        uploaded,
        failed,
      };
    } catch (error) {
      this.logger.error('批量上传资源失败:', error);
      return {
        success: false,
        uploaded: [],
        failed: [],
        error: error.message,
      };
    }
  }

  /**
   * 从Minio删除项目相关文件
   */
  async deleteProjectFromMinio(projectId: string): Promise<{
    success: boolean;
    deleted?: number;
    error?: string;
  }> {
    if (!this.minioService.isAvailable()) {
      return {
        success: false,
        error: 'Minio服务不可用',
      };
    }

    try {
      await this.minioService.deleteProjectFiles(projectId);
      this.logger.log(`已删除项目 ${projectId} 的Minio文件`);
      return { success: true, deleted: 1 };
    } catch (error) {
      this.logger.error(`删除项目 ${projectId} 的Minio文件失败:`, error);
      return {
        success: false,
        error: error.message,
      };
    }
  }

  /**
   * 获取项目在Minio中的存储信息
   */
  async getProjectMinioInfo(projectId: string): Promise<{
    success: boolean;
    objects?: any[];
    totalSize?: number;
    objectCount?: number;
    error?: string;
  }> {
    if (!this.minioService.isAvailable()) {
      return {
        success: false,
        error: 'Minio服务不可用',
      };
    }

    try {
      const objects = await this.minioService.listProjectResources(projectId);
      const totalSize = objects.reduce((sum, obj) => sum + (obj.size || 0), 0);
      const objectCount = objects.length;

      return {
        success: true,
        objects,
        totalSize,
        objectCount,
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }

  /**
   * 定期清理过期的临时文件
   */
  async cleanupExpiredFiles(): Promise<void> {
    try {
      // 使用项目根目录计算路径
      const projectRoot = path.resolve(__dirname, '../../../../../');
      const publicDir = path.join(projectRoot, 'public', 'temp');

      console.log(`清理过期文件，检查目录: ${publicDir}`);

      if (!fs.existsSync(publicDir)) {
        console.log('临时目录不存在，跳过清理');
        return;
      }

      const files = fs.readdirSync(publicDir);
      const now = Date.now();
      const expireTime = 24 * 60 * 60 * 1000; // 24小时

      files.forEach((file) => {
        const filePath = path.join(publicDir, file);
        const stats = fs.statSync(filePath);

        if (now - stats.mtime.getTime() > expireTime) {
          fs.unlinkSync(filePath);
          console.log(`已清理过期文件: ${file}`);
        }
      });
    } catch (error) {
      console.error('清理过期文件失败:', error);
    }
  }
}
